f426e2d08db976900dfc91852484298364bcf356,src/dorkbox/systemTray/SystemTray.java,SystemTray,init,#boolean#,247

Before Change


                FORCE_TRAY_TYPE = TrayType.AutoDetect;
            }
        }
        else if (OS.isLinux()) {
            // kablooie if SWT/JavaFX is not configured in a way that works with us.
            if (FORCE_TRAY_TYPE != TrayType.Swing) {
                if (isSwtLoaded) {

After Change


                FORCE_TRAY_TYPE = TrayType.AutoDetect;
            }
        }
        else if (OS.isLinux() || OS.isUnix()) {
            // kablooie if SWT/JavaFX is not configured in a way that works with us.
            if (FORCE_TRAY_TYPE != TrayType.Swing) {
                if (isSwtLoaded) {
                    // Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
                    // System.setProperty("SWT_GTK3", "0");

                    // was SWT forced?
                    String swt_gtk3 = System.getProperty("SWT_GTK3");
                    boolean isSwt_GTK3 = swt_gtk3 != null && !swt_gtk3.equals("0");
                    if (!isSwt_GTK3) {
                        // check a different property
                        String property = System.getProperty("org.eclipse.swt.internal.gtk.version");
                        isSwt_GTK3 = property != null && !property.startsWith("2.");
                    }

                    if (isSwt_GTK3 && FORCE_GTK2) {
                        logger.error("Unable to use the SystemTray when SWT is configured to use GTK3 and the SystemTray is configured to use " +
                                     "GTK2. Please configure SWT to use GTK2, via `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is " +
                                     "initialized, or set `SystemTray.FORCE_GTK2=false;`");

                        systemTrayMenu = null;
                        systemTray = null;
                        return;
                    } else if (!isSwt_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
                        // we must use GTK2, because SWT is GTK2
                        logger.warn("Forcing GTK2 because SWT is GTK2");
                        FORCE_GTK2 = true;
                    }
                }
                else if (isJavaFxLoaded) {
                    // JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified
                    // see
                    // http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
                    // https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
                    // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
                    boolean isJFX_GTK3 = System.getProperty("jdk.gtk.version", "2").equals("3");
                    if (isJFX_GTK3 && FORCE_GTK2) {
                        // if we are java9, then we can change it -- otherwise we cannot.
                        if (OS.javaVersion == 9 && AUTO_FIX_INCONSISTENCIES) {
                            logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " +
                                        "configured to use GTK2. Please configure JavaFX to use GTK2 (via `System.setProperty(\"jdk.gtk.version\", \"3\");`) " +
                                        "before JavaFX is initialized, or set `SystemTray.FORCE_GTK2=false;`  Undoing `FORCE_GTK2`.");

                            FORCE_GTK2 = false;
                        } else {
                            logger.error("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is configured to use " +
                                         "GTK2. Please set `SystemTray.FORCE_GTK2=false;`  if that is not possible then it will not work.");

                            systemTrayMenu = null;
                            systemTray = null;
                            return;
                        }
                    } else if (!isJFX_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
                        // we must use GTK2, because JavaFX is GTK2
                        logger.warn("Forcing GTK2 because JavaFX is GTK2");
                        FORCE_GTK2 = true;
                    }
                }
            }
        }

        Class<? extends Tray> trayType = null;

        if (DEBUG) {
            logger.debug("OS: {}", System.getProperty("os.name"));
            logger.debug("Arch: {}", System.getProperty("os.arch"));

            String jvmName = System.getProperty("java.vm.name", "");
            String jvmVersion = System.getProperty("java.version", "");
            String jvmVendor = System.getProperty("java.vm.specification.vendor", "");
            logger.debug("{} {} {}", jvmVendor, jvmName, jvmVersion);


            logger.debug("Is AutoTraySize? {}", AUTO_TRAY_SIZE);
            logger.debug("Is JavaFX detected? {}", isJavaFxLoaded);
            logger.debug("Is SWT detected? {}", isSwtLoaded);
            logger.debug("Is using native menus? {}", useNativeMenus);
            logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
            logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
        }

        // Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
        // mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
        // all examined ones sometimes have it (and it's more than just text), or they don't have it at all. There is no mouse-over event.


        // this has to happen BEFORE any sort of swing system tray stuff is accessed
        if (OS.isWindows()) {
            // windows is funky, and is hardcoded to 16x16. We fix that.
            SystemTrayFixes.fixWindows();
        }
        else if (OS.isMacOsX() && useNativeMenus) {
            // macosx doesn't respond to all buttons (but should)
            SystemTrayFixes.fixMacOS();
        }
        else if ((OS.isLinux() || OS.isUnix()) && FORCE_TRAY_TYPE != TrayType.Swing) {
            // see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running

            // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
            // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py

            // this can never be swing
            // don't check for SWING type at this spot, it is done elsewhere.
            if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) {
                trayType = selectTypeQuietly(useNativeMenus, SystemTray.FORCE_TRAY_TYPE);
            }


            // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least

            // if we are running as ROOT, we *** WILL NOT *** have access to  'XDG_CURRENT_DESKTOP'
            //   *unless env's are preserved, but they are not guaranteed to be
            // see:  http://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
            String XDG = System.getenv("XDG_CURRENT_DESKTOP");
            if (XDG == null) {
                // maybe we are running as root???
                XDG = "unknown"; // try to autodetect if we should use app indicator or gtkstatusicon
            }


            // BLEH. if gnome-shell is running, IT'S REALLY GNOME!
            // we must ALWAYS do this check!!
            boolean isReallyGnome = OSUtil.DesktopEnv.isGnome();

            if (isReallyGnome) {
                if (DEBUG) {
                    logger.error("Auto-detected that gnome-shell is running");
                }
                XDG = "gnome";
            }

            if (DEBUG) {
                logger.debug("Currently using the '{}' desktop", XDG);
            }

            if (trayType == null) {
                // Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
                if ("unity".equalsIgnoreCase(XDG)) {
                    trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
                }
                else if ("xfce".equalsIgnoreCase(XDG)) {
                    // NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
                    // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
                    // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25

                    // so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
                    trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                }
                else if ("lxde".equalsIgnoreCase(XDG)) {
                    trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                }
                else if ("kde".equalsIgnoreCase(XDG)) {
                    if (OSUtil.Linux.isFedora()) {
                        // Fedora KDE requires GtkStatusIcon
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                    } else {
                        // kde (at least, plasma 5.5.6) requires appindicator
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
                    }

                    // kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
                }
                else if ("pantheon".equalsIgnoreCase(XDG)) {
                    // elementaryOS. It only supports appindicator (not gtkstatusicon)
                    // http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/view/head:/sample/SampleIndicator.vala

                    if (!useNativeMenus && AUTO_FIX_INCONSISTENCIES) {
                        logger.warn("Cannot use non-native menus with pantheon DE. Forcing native menus.");
                        useNativeMenus = true;
                    }

                    // ElementaryOS shows the checkbox on the right, everyone else is on the left.
                    // With eOS, we CANNOT show the spacer image. It does not work
                    trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
                }
                else if ("gnome".equalsIgnoreCase(XDG)) {
                    // check other DE
                    String GDM = System.getenv("GDMSESSION");

                    if (DEBUG) {
                        logger.debug("Currently using the '{}' session type", GDM);
                    }

                    if ("gnome".equalsIgnoreCase(GDM)) {
                        if (OSUtil.Linux.isArch()) {
                            if (DEBUG) {
                                logger.debug("Running Arch Linux.");
                            }
                            if (!Extension.isInstalled()) {
                                logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " +
                                            "the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " +
                                            "icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " +
                                            "of the screen to the menu panel next to the clock.");
                            }
                        } else {
                            // Automatically install the extension for everyone except Arch. It's bonkers.
                            Extension.install();
                        }

                        // are we fedora? If so, what version?
                        // now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon
                        if (OSUtil.Linux.isFedora()) {
                            if (DEBUG) {
                                logger.debug("Running Fedora");
                            }

                            // 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this)
                            trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                        }
                        else if (OSUtil.Linux.isUbuntu()) {
                            // so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works.
                            trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                        }
                        else if (OSUtil.Unix.isFreeBSD()) {
                            trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                        }
                        else {
                            // arch likely will have problems unless the correct/appropriate libraries are installed.
                            trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
                        }
                    }
                    else if ("cinnamon".equalsIgnoreCase(GDM)) {
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                    }
                    else if ("gnome-classic".equalsIgnoreCase(GDM)) {
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                    }
                    else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                    }
                    else if ("ubuntu".equalsIgnoreCase(GDM)) {
                        trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
                    }
                }
            }

            // Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
            if (trayType == null) {
                BufferedReader bin = null;
                try {
                    // the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
                    // is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli
                    File proc = new File("/proc");
                    File[] listFiles = proc.listFiles();
                    if (listFiles != null) {
                        for (File procs : listFiles) {
                            String name = procs.getName();

                            if (!Character.isDigit(name.charAt(0))) {
                                continue;
                            }

                            File status = new File(procs, "status");
                            if (!status.canRead()) {
                                continue;
                            }

                            try {
                                bin = new BufferedReader(new FileReader(status));
                                String readLine = bin.readLine();

                                if (readLine != null && readLine.contains("indicator-app")) {
                                    // make sure we can also load the library (it might be the wrong version)
                                    try {
                                        trayType = selectType(useNativeMenus, TrayType.AppIndicator);
                                    } catch (Exception e) {
                                        if (DEBUG) {
                                            logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e);
                                        } else {
                                            logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
                                        }
                                    }
                                    break;
                                }
                            } finally {
                                IO.closeQuietly(bin);
                            }
                        }
                    }
                } catch (Throwable e) {
                    if (DEBUG) {
                        logger.error("Error detecting gnome version", e);
                    }
                }
            }


            // fallback...
            if (trayType == null) {
                trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
                logger.warn("Unable to determine the system window manager type. Falling back to GtkStatusIcon.");
            }

            // this is bad...
            if (trayType == null) {
                logger.error("SystemTray initialization failed. Unable to load the system tray native libraries. Please write an issue " +
                             "and include your OS type and configuration");

                systemTrayMenu = null;
                systemTray = null;
                return;
            }

            if (isTrayType(trayType, TrayType.AppIndicator)) {
                // if are we running as ROOT, there can be issues (definitely on Ubuntu 16.04, maybe others)!

                // this means we are running as sudo
                String sudoUser = System.getenv("SUDO_USER");
                if (sudoUser != null) {
                    // running as a "sudo" user
                    logger.error("Attempting to load the SystemTray as the 'root' user. This will likely not work because of dbus restrictions.");
                }
                else {
                    // running as root (also can be "sudo" user). A bit slower that checking a sys env, but this is guaranteed to work
                    try {
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
                        PrintStream outputStream = new PrintStream(byteArrayOutputStream);

                        // id -u
                        final ShellProcessBuilder shell = new ShellProcessBuilder(outputStream);
                        shell.setExecutable("id");
                        shell.addArgument("-u");
                        shell.start();


                        String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
                        if ("0".equals(output)) {
                            logger.error("Attempting to load the SystemTray as the 'root' user. This will likely not work because of dbus " +
                                         "restrictions.");
                        }
                    } catch (Throwable e) {
                        if (DEBUG) {
                            logger.error("Cannot get id for root", e);
                        }
                    }
                }
            }
        }

        // this is likely windows OR mac
        if (trayType == null) {
            try {
                trayType = selectType(useNativeMenus, TrayType.Swing);
            } catch (Throwable e) {
                if (DEBUG) {
                    logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.", e);
                } else {
                    logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.");
                }
            }
        }

        if (trayType == null) {
            // unsupported tray, or unknown type
            logger.error("SystemTray initialization failed. (Unable to discover which implementation to use). Something is seriously wrong.");

            systemTrayMenu = null;
            systemTray = null;
            return;
        }

        ImageUtils.determineIconSize();

        final AtomicReference<Tray> reference = new AtomicReference<Tray>();

        // - appIndicator/gtk require strings (which is the path)
        // - swing version loads as an image (which can be stream or path, we use path)
        CacheUtil.tempDir = "SysTray";

        try {
            if (OS.isLinux() || OS.isUnix()) {
                // NOTE:  appindicator1 -> GTk2, appindicator3 -> GTK3.
                // appindicator3 doesn't support menu icons via GTK2!!
                if (!Gtk.isLoaded) {